#include <QtCore/QTimer>
#include <QtSql/QSqlError>
#include <QtSql/QSqlRecord>
#include "dbmanager/database_manager_p.h"
#include "dbmanager/query_handler.h"
#include "kernel/kernel.h"

namespace smsbilling {

SqlQuery::SqlQuery(bool return_result, bool use_transaction) :
  success(false),
  use_transaction(use_transaction),
  return_result(return_result),
  fault_code(-1) {

}

SqlQuery::~SqlQuery() {

}

void SqlQuery::add(const QString &query) {
  queries.push_back(query);
}

QueryHandler::QueryHandler(int id,
                           DatabaseManagerPrivate *parent,
                           int exec_timeout) :
  database(0),
  exec_timeout(exec_timeout),
  parent(parent),
  db_name(QString("QueryHandler_").append(QString::number(id))) {
  moveToThread(this);
  connect(this, SIGNAL(started()), this, SLOT(threadStarted()));
  connect(this, SIGNAL(finished()), this, SLOT(threadStoped()));
  connect(this, SIGNAL(terminated()), this, SLOT(threadStoped()));
}

QueryHandler::~QueryHandler() {

}

void QueryHandler::threadStarted() {
  createDatabase();

  if(exec_timeout > 0) {
    QTimer::singleShot(exec_timeout, this, SLOT(execNextQuery()));
  }
}

void QueryHandler::threadStoped() {
  destroyDatabase();
}

void QueryHandler::execNextQuery() {
  parent->mutex.lock();
  if(parent->queries.isEmpty() == false) {
    SqlQuery *query = parent->queries.takeFirst();
    parent->mutex.unlock();

    exec(query);

    while(query->success == false &&
          (query->fault_code == 1213 || query->fault_code == 1205)) {
      exec(query);
    }

    //qDebug() << QThread::currentThreadId() << query->query_string;

    emit query->executed();
  } else {
    parent->mutex.unlock();
  }

  if(exec_timeout > 0) {
    QTimer::singleShot(exec_timeout, this, SLOT(execNextQuery()));
  }
}

void QueryHandler::createDatabase() {
  database = new QSqlDatabase(QSqlDatabase::addDatabase("QMYSQL", db_name));

  database->setHostName(Kernel::instance()->getDbHost());
  database->setPort(Kernel::instance()->getDbPort());
  database->setDatabaseName(Kernel::instance()->getDbName());
  database->setUserName(Kernel::instance()->getDbUserName());
  database->setPassword(Kernel::instance()->getDbPassword());
}

void QueryHandler::destroyDatabase() {
  if(database) {
    if(database->isOpen()) {
      database->close();
    }

    delete database;
    database = 0;

    QSqlDatabase::removeDatabase(db_name);
  }
}

bool QueryHandler::checkAndOpenDb() {
  bool res(database->isOpenError());
  if(res) {
    database->close();
    res = database->open();
  } else if(!database->isOpen()) {
    res = database->open();
  } else {
    res = true;
  }

  return res;
}

void QueryHandler::exec(SqlQuery *query) {
  if(checkAndOpenDb()) {
    if(query->use_transaction) {
      if(!database->transaction()) {
        query->success = false;
        query->fault_code = database->lastError().number();
        query->fault_string = database->lastError().text();
        parent->faultExecute(query->fault_code);
        return;
      }
    }

    QSqlQuery *q = new QSqlQuery(*database);

    for(int i = 0; i < query->queries.size(); i++) {
      if(q->exec(query->queries[i])) {
        if(q->isActive()) {
          if(query->return_result) {
            while(q->next()) {
              query->records.push_back(q->record());
            }
          }
          q->clear();
        } else {
          query->success = false;
          query->fault_code = q->lastError().number();
          query->fault_string = q->lastError().text();
          parent->faultExecute(query->fault_code);
          if(query->use_transaction) {
            database->rollback();
          }
          delete q;
          //database->close();
          return;
        }
      } else {
        query->success = false;
        query->fault_code = q->lastError().number();
        query->fault_string = q->lastError().text();
        parent->faultExecute(query->fault_code);
        if(query->use_transaction) {
          database->rollback();
        }
        delete q;
        //database->close();
        return;
      }
    }

    delete q;
  } else {
    query->success = false;
    query->fault_code = database->lastError().number();
    query->fault_string = database->lastError().text();
    parent->faultExecute(query->fault_code);
    return;
  }

  if(query->use_transaction) {
    if(database->commit()) {
      query->success = true;
      parent->mutex.lock();
      parent->executed_success++;
      parent->mutex.unlock();
    } else {
      query->success = false;
      query->fault_code = database->lastError().number();
      query->fault_string = database->lastError().text();
      parent->faultExecute(query->fault_code);
    }
  } else {
    query->success = true;
    parent->mutex.lock();
    parent->executed_success++;
    parent->mutex.unlock();
  }

  //database->close();
}

DatabaseManagerPrivate::DatabaseManagerPrivate() :
  executed_success(0) {
  for(int i = 0; i < Kernel::instance()->getNumConnectDb(); i++) {
    QueryHandler *handler = new QueryHandler(
      i, this, Kernel::instance()->getExecQueryTimeout());

    handlers.push_back(handler);

    handler->start();
  }
}

DatabaseManagerPrivate::~DatabaseManagerPrivate() {
  for(int i = 0; i < handlers.size(); i++) {
    QueryHandler *handler = handlers[i];

    if(handler->isRunning()) {
      handler->quit();
      handler->wait();
    }

    delete handler;
  }
}

void DatabaseManagerPrivate::faultExecute(int code) {
  mutex.lock();
  if(executed_fault.contains(code)) {
    executed_fault[code]++;
  } else {
    executed_fault.insert(code, 1);
  }
  mutex.unlock();
}

DatabaseManager::DatabaseManager() : p(new DatabaseManagerPrivate) {

}

DatabaseManager::~DatabaseManager() {
  if(p) {
    delete p;
    p = 0;
  }
}

void DatabaseManager::execute(SqlQuery *query) {
  p->mutex.lock();
  p->queries.push_back(query);
  p->mutex.unlock();
}

} // namespace smsbilling
